$LOAD_PATH << "../../lib"

require "starruby"
require "starframe"

# スプライト定義
#   キャラクタ（自機および敵機）
class Character < StarFrame::Sprite
  include StarFrame::Sprite::Animatable
  include StarFrame::Sprite::Collidable
  
  def init options
    @scene            = options[:scene]
    @shot_collection  = options[:shot_collection]
    @smoke_collection = options[:smoke_collection]
  end
  
  # 発射
  # 指定された弾を追加
  def shot shot_sprite
    @shot_collection << shot_sprite
  end
  
  # 撃墜
  # 爆煙を追加して消滅
  def crash
    @smoke_collection << Smoke.new(@x, @y)
    StarRuby::Audio.play_se @crash_se
    @scene.score += @score
    vanish
  end
end
#     自機
class Player < Character
  @texture = StarRuby::Texture.load("player")
  @center_x = 16
  @center_y = 16+6
  
  attr_accessor :stelth_ready, :readying, :stelth
  
  animation :burner do
    frames [0, 32], 2 do |c|
      # このアニメーション方式は定型に？
      self.src_y = c
    end
  end
  animation :shot, :loop => false, :start => false do
    frames 3, 2 do
      shot Shot::Player.new(@x-8, @y-16)
      shot Shot::Player.new(@x+8, @y-16)
    end
  end
  animation :ready, :loop => false, :start => false do
    frame do
      @x = 160
      @y = 520
      @readying = true
      @stelth = true
      self.alpha = 0xFF
      animation(:stelth).stop
    end
    frames 40.decelerate do |c|
      @y = 520-c*2
    end
    frame do
      @y = 440
      @readying = false
      self.stelth = @stelth_ready
    end
    wait 60
    frame do
      self.stelth = false
    end
  end
  animation :stelth, :start => false do
    frames [0x00, 0xFF] do |c|
      self.alpha = c
    end
  end
  
  collision :damage, StarFrame::Sprite::Collision::Rect, -2, -2, 4, 4 do |other|
    crash
  end
  collision :graze, StarFrame::Sprite::Collision::Rect, -8, -8, 16, 16 do |other|
    @scene.score += 5
    # カスリアニメーションを動的に生成できるようにしたい
    @scene.foreground.render_rect other.x-3, other.y-3, 7, 7, StarRuby::Color.new(0xFF, 0xFF, 0xFF)
    StarRuby::Audio.play_se "tm2_shoot000" if StarRuby::Audio.playing_se_count < 4
  end
  
  def init *args
    self.src_height = 32
    @crash_se = "tm2r_bom34"
    @score = -1000
    super
    ready false
  end
  
  def update
    if @x < 0
      @x = 0
    elsif @x > 320
      @x = 320
    end
    if @y < 0
      @y = 0
    elsif @y > 480
      @y = 480
    end
  end
  
  # 無敵モードON/OFF
  def stelth= value
    if value
      animation(:stelth).restart
    else
      animation(:stelth).stop
      self.alpha = 0xFF
    end
    @stelth = value
  end
  
  # 発射
  def fire
    animation(:shot).start
    StarRuby::Audio.play_se "tm2_gun001"
  end
  
  # 準備
  def ready stelth_ready = true
    @stelth_ready = stelth_ready
    animation(:ready).restart
  end
  
  # 撃墜
  def crash
    super
    ready
  end
end
#     敵機
class Enemy < Character
  attr_accessor :score
  
  def init options
    @target = options[:target]
    @crash_se = "tm2_bom001"
    
    @shot_point_x =  0
    @shot_point_y = 24
    super
  end
  
  def update
    @to_target_angle = nil
  end
  
  # 自機との角度
  def to_target_angle
    unless @to_player_angle
      @to_target_angle = Math.atan((shot_y - @target.y).quo(shot_x - @target.x))
      @to_target_angle += 180.degrees if @target.x < shot_x
    end
    @to_target_angle
  end
  def shot_x
    @x + @shot_point_x
  end
  def shot_y
    @y + @shot_point_y
  end
  
  # 自機狙い弾
  def fire_to_player speed = 3, add_angle = 0
    enemy_shot = Shot::Enemy.new(shot_x, shot_y)
    shot_angle = to_target_angle + add_angle
    enemy_shot.movement_x = Math.cos(shot_angle) * speed
    enemy_shot.movement_y = Math.sin(shot_angle) * speed
    shot enemy_shot
  end
  
  # 敵機名：ディスク
  class Disc < Enemy
    @texture = StarRuby::Texture.load("enemy")
    @center_x = 16
    @center_y = 16
    
    animation :burner do
      frames [0, 32], 2 do |c|
        self.src_y = c
      end
    end
    animation :move, :loop => false do
      frames 128.decelerate do |c|
        @y = -32+c
      end
      frame do
        @y = 96
      end
      
      3.times do |i|
        wait 18 unless i.zero?
        frames 3, 4 do
          fire
        end
      end
      
      parallel do
        frame do
          @score = 200
        end
        frames 32.accelerate do |c|
          @y = 96-c*4
        end
        frames 32 do
          @x += 1
        end
      end
      frame do
        vanish
      end
    end
    
    collision :body, StarFrame::Sprite::Collision::Rect, -16, -16, 32, 32 do |other|
      # @scene.render_number @scene.foreground, @x-32, @y-32, score
      crash
    end
    
    def fire
      # fire_to_player 3,  10.degrees
      fire_to_player 3
      # fire_to_player 3, -10.degrees
    end
    
    def init *args
      @score = 100
      @shot_speed = 3
      self.src_height = 32
      self.tone_red  =  0x33
      self.tone_blue = -0x33
      super
    end
  end
end

class Smoke < StarFrame::Sprite
  @texture = StarRuby::Texture.load("smoke")
  @center_x = 16
  @center_y = 16
  
  include StarFrame::Sprite::Animatable
  animation :smoke do
    frames Array.new(18){ |i| i*32 }, 2 do |c|
      self.src_x = c
    end
    frame do
      vanish
    end
  end
  
  include StarFrame::Sprite::Collidable
  collision :attack, StarFrame::Sprite::Collision::Rect, -16, -16, 32, 32
  
  def init *args
    self.src_width = 32
  end
end

class Shot < StarFrame::Sprite
  @texture = StarRuby::Texture.load("shot")
  @center_x = 4
  @center_y = 4
  
  include StarFrame::Sprite::Animatable
  animation :rolling do
    frames (0...8), 2 do |c|
      self.src_x = c*8
    end
  end
  
  include StarFrame::Sprite::Collidable
  collision :attack, StarFrame::Sprite::Collision::Rect, -4, -4, 8, 8 do |other|
    vanish
  end
  collision :graze, StarFrame::Sprite::Collision::Rect, -4, -4, 8, 8
  
  def init *args
    self.src_width = 8
  end
  
  def update
    if @x < -64 || @x > 384 ||
       @y < -64 || @y > 544
      vanish
    end
  end
  
  class Player < Shot
    def init *args
      self.movement_y = -4
      super
    end
  end
  
  class Enemy < Shot
    def init *args
      self.tone_blue =  0x99
      self.tone_red  = -0x66
      super
    end
  end
end

# シーン定義
class MainScene < StarFrame::Scene
  attr_reader :foreground
  
  include StarFrame::Sprite::Animatable
  animation :scroll do
    frames (0...544) do |c|
      @bg_scroll = c
    end
  end
  animation :enemy do
    frame 30 do
      @enemies << Enemy::Disc.new(16+rand(320-32), -64, @enemy_options)
    end
  end
  
  def init *args
    srand 0
    
    @shots       = StarFrame::Sprite::Collection.new
    @smokes      = StarFrame::Sprite::Collection.new
    @enemies     = StarFrame::Sprite::Collection.new
    @enemy_shots = StarFrame::Sprite::Collection.new
    
    @player = Player.new(-64, -64,        :scene => self, :shot_collection => @shots,       :smoke_collection => @smokes)
    @enemy_options = {:target => @player, :scene => self, :shot_collection => @enemy_shots, :smoke_collection => @smokes}
    
    @score = 0
    @frame = 0
    @pause = false
    
    @numbers = StarRuby::Texture.load("numbers")
    @background = StarRuby::Texture.load("bg")
    @foreground = StarRuby::Texture.new(@screen.width, @screen.height)
    
    @pause_screen = StarRuby::Texture.new(@screen.width, @screen.height)
    @pause_screen.fill StarRuby::Color.new(0, 0, 0)
    pause_logo = StarRuby::Texture.load("pause")
    @pause_screen.render_texture pause_logo, (@screen.width-pause_logo.width)/2, (@screen.height-pause_logo.height)/2
  end
  
  def update
    # ゲーム終了・ポーズ・無敵モードON/OFF
    exit_scene if StarRuby::Input.keys(:keyboard).include? :escape
    input_special_keys = StarRuby::Input.keys(:gamepad, :duration => 1)
    if input_special_keys.include? 9
      @pause = !@pause
      if @pause
        animation(:scroll).stop
        animation(:enemy).stop
      else
        animation(:scroll).start
        animation(:enemy).start
      end
    end
    @player.stelth = !@player.stelth if input_special_keys.include? 10
    return if @pause
    
    @foreground.clear
    
    # 自弾と敵機（敵機に二つの自弾が当たってしまわないように）
    @shots.collide @enemies, :attack, :body
    # 爆煙と敵機
    @smokes.collide @enemies, :attack, :body
    # 自機と
    unless @player.stelth
      catch :vanished do
        # 敵機
        @enemies.each do |enemy|
          throw :vanished if enemy.collide @player, :body, :damage
        end
        # 敵弾
        @enemy_shots.each do |enemy_shot|
          # 命中
          throw :vanished if @player.collide enemy_shot, :damage, :attack
          # カスり
          @player.collide enemy_shot, :graze, :graze
        end
      end
    end
    
    # 自機移動
    movement_x = 0
    movement_y = 0
    angle = 0
    scale_x = 1
    unless @player.readying
      input_move_keys = StarRuby::Input.keys(:gamepad)
      # 高速／低速移動
      if (input_move_keys & [5, 6, 7, 8]).empty?
        speed = 3
      else
        speed = 1
      end
      # Up Down
      if input_move_keys.include? :up
        movement_y = -speed
      elsif input_move_keys.include? :down
        movement_y = speed
      end
      # Left Right
      if input_move_keys.include? :left
        movement_x = -speed
        angle = -4.degrees
        scale_x = 0.90
      elsif input_move_keys.include? :right
        movement_x = speed
        angle = 4.degrees
        scale_x = 0.90
      end
      # Shot::Player
      input_shot_keys = StarRuby::Input.keys(:gamepad, :duration => 1, :delay => 8, :interval => 8)
      @player.fire unless (input_shot_keys & [1, 2, 3, 4]).empty?
    end
    @player.movement_x = movement_x
    @player.movement_y = movement_y
    @player.angle = angle
    @player.scale_x = scale_x
    
    # 各種更新
    @player.call
    @shots.call
    @enemy_shots.call
    @enemies.call
    @smokes.call
  end
  
  def render
    @screen.render_texture @background, 0, @bg_scroll
    @screen.render_texture @background, 0, @bg_scroll-544
    
    @enemies.render_to @screen
    @player.render_to @screen
    
    @shots.render_to @screen
    @enemy_shots.render_to @screen
    @smokes.render_to @screen
    
    render_number @screen, 96, 8, @score
    render_number @screen, 304, 8, @game.real_fps.to_i
    
    @screen.render_texture @foreground, 0, 0
    
    @screen.render_texture @pause_screen, 0, 0, :alpha => 0x7F if @pause
  end
  
  def render_number screen, x, y, number
    digit = 0
    loop do
      score_digit = number%10
      screen.render_texture @numbers, x-(8*digit), y, :src_x => (8*score_digit), :src_width => 8
      digit += 1
      number /= 10
      break if number == 0
    end
  end
  
  attr_accessor :score
  def score= score
    @score = score
    @score = 0 if @score < 0
  end
end

StarFrame.start :width => 320, :height => 480,
                :title => "STG", :fps => 60, :fullscreen => false
